using System;
using System.Collections.Generic;
using System.Text;

namespace csammisrun.OscarLib.Utility
{
	/// <summary>
	/// Provides static methods for processing messages in SNAC family 0x0004 -- ICBM service
	/// </summary>
	internal static class SNAC04
	{
    /// <summary>
		/// Sets ICBM parameters to more reasonable values than the defaults -- SNAC(04,02)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		public static void UpdateParameters(Session sess)
		{
			// Construct the SNAC header
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
			sh.FamilySubtypeID = (ushort)ICBMService.SetICBMParameters;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			// Put the ICBM parameters into a byte array
			int index = 0;
			byte[] buffer = new byte[16];
			// Channel to setup -- should always be 0
			Marshal.InsertUshort(buffer, 0x0000, ref index);
			// Message flags
			Marshal.InsertUint(buffer, 0x0000000B, ref index);
			// Maximum message size (8192 bytes)
      sess.Limits.MaxMessageSize = 0x1F40;
			Marshal.InsertUshort(buffer, 0x1F40, ref index);
			// Maximum sender evil level
      sess.Limits.MaxSenderWarningLevel = 0x0384;
			Marshal.InsertUshort(buffer, 0x0384, ref index);
			// Maximum receiver evil level
      sess.Limits.MaxReceiverWarningLevel = 0x03E7;
			Marshal.InsertUshort(buffer, 0x03E7, ref index);
			// Minimum message interval, in seconds
			Marshal.InsertUint(buffer, 0x00000000, ref index);

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Resets ICBM parameters to their default values -- SNAC(04,03)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		public static void ResetICBMParameters(Session sess)
		{
			// Build the SNAC header
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
			sh.FamilySubtypeID = (ushort)ICBMService.ResetICBMParamters;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			// No SNAC data
			DataPacket dp = Marshal.BuildDataPacket(sess, sh, null);
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Sends a request for parameter information -- SNAC(04,04)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		public static void RequestParametersList(Session sess)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
			sh.FamilySubtypeID = (ushort)ICBMService.ParametersInformationRequest;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, null);
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Processes the parameter information sent by the server -- SNAC(04,05)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(04,05)</param>
		public static void ProcessParametersList(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			ushort max_channel =		Marshal.ByteArrayToUshort(SNACData, ref index);
			uint flags =				Marshal.ByteArrayToUshort(SNACData, ref index);
			ushort snac_size =			Marshal.ByteArrayToUshort(SNACData, ref index);
			ushort sender_evil =		Marshal.ByteArrayToUshort(SNACData, ref index);
			ushort receiver_evil =		Marshal.ByteArrayToUshort(SNACData, ref index);
			ushort message_interval =	Marshal.ByteArrayToUshort(SNACData, ref index);
			ushort unknown_data =		Marshal.ByteArrayToUshort(SNACData, ref index);

			// Do something with these capabilities
			dp.ParentSession.ParameterSetArrived();
			// Send SNAC(04,02) to rid ourselves of these foolish default settings
			SNAC04.UpdateParameters(dp.ParentSession);
		}

		#region SNAC(04,06) -- Send ICBM
		/// <summary>
		/// Sends an ICBM on channel 1 -- SNAC(04,06)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="destination">The screenname to receive the IM</param>
		/// <param name="message">The message to send</param>
		/// <param name="flags">A <see cref="MessageFlags"/> enumeration specifying what
		/// additional information should be sent with the message</param>
		public static void SendMessage(
			Session sess,
			string destination,
			string message,
      Encoding encoding,
			MessageFlags flags)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
			sh.FamilySubtypeID = (ushort)ICBMService.SendMessageThroughServer;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			byte[] default_features = new byte[] {0x01, 0x01, 0x01, 0x02};
			byte[] cookie = new byte[8];
			Random r = new Random(); r.NextBytes(cookie);

      byte destlength = (byte)encoding.GetByteCount(destination);
      ushort messagelength = (ushort)encoding.GetByteCount(message);

			uint buffersize = 0;
			ushort tlv2size = 0;

			// Size of the ICBM header (cookie, channel, sn size, sn)
			buffersize += (uint)(8 + 2 + 1 + destlength);
			// Size of the TLV 0x0002
			buffersize += 2 + 2;
			tlv2size += (ushort)(1 + 1 + 2 + default_features.Length);	// 0501 feature
			tlv2size += (ushort)(1 + 1 + 2 + 4 + messagelength);	// 0101 feature
			buffersize += tlv2size;
			// Size of the optional flags
			if((flags & MessageFlags.AutoResponse) != 0)
				buffersize += 4;
			if((flags & MessageFlags.RequestAcknowledgement) != 0)
				buffersize += 4;
			if((flags & MessageFlags.HasIcon) != 0)
				buffersize += 2 + 2 + 4 + 2 + 2 + 4;
			if((flags & MessageFlags.RequestIcon) != 0)
				buffersize += 4;

			// Allocate the buffer and start packing in data
			int index = 0;
			byte[] buffer = new byte[buffersize];
			// ICBM header
			Marshal.CopyArray(cookie, buffer, 0, ref index);
			Marshal.InsertUshort(buffer, 0x0001, ref index);
      buffer[index++] = destlength;
      Marshal.InsertString(buffer, destination, encoding, ref index);
			// TLV 0x0002 header
			Marshal.InsertUshort(buffer, 0x0002, ref index);
			Marshal.InsertUshort(buffer, tlv2size, ref index);
			// Feature 0501
			Marshal.InsertUint(buffer, 0x05010004, ref index);
			Marshal.CopyArray(default_features, buffer, 0, ref index);
			// Feature 0101
			Marshal.InsertUshort(buffer, 0x0101, ref index);
			Marshal.InsertUshort(buffer, (ushort)(messagelength + 0x04), ref index);
      Marshal.InsertUshort(buffer, Marshal.EncodingToCharset(encoding), ref index);
      Marshal.InsertUshort(buffer, (ushort)0x0000, ref index);
      Marshal.InsertString(buffer, message, encoding, ref index);
			// Pack in optional TLVs
			if((flags & MessageFlags.AutoResponse) != 0)
			{
				Marshal.InsertUint(buffer, 0x00040000, ref index);
			}
			if((flags & MessageFlags.RequestAcknowledgement) != 0)
			{
				Marshal.InsertUint(buffer, 0x00030000, ref index);
			}
			if((flags & MessageFlags.HasIcon) != 0)
			{
        Marshal.InsertUint(buffer, 0x0008000C, ref index);
        // We should really do null checks here, in case the client is an idiot
        Marshal.InsertUint(buffer, sess.SSI.IconItem.IconSize, ref index);
        Marshal.InsertUshort(buffer, 0x0001, ref index);
        Marshal.InsertUshort(buffer, sess.SSI.IconItem.Checksum, ref index);
        Marshal.InsertUint(buffer, sess.SSI.IconItem.Stamp, ref index);
			}
			if((flags & MessageFlags.RequestIcon) != 0)
			{
				Marshal.InsertUint(buffer, 0x00090000, ref index);
			}

			// Send that sucker off
			DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Sends a chat invitation on channel 2 -- SNAC(04,06)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="destination">The screenname to receive the invitation</param>
		/// <param name="message">The message to send along with the invitation</param>
    /// <param name="roominfo">A populated <see cref="ChatRoomInfo"/> object</param>
		public static void SendChatInvitation(
			Session sess,
			string destination,
			string message,
      ChatRoomInfo roominfo)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
			sh.FamilySubtypeID = (ushort)ICBMService.SendMessageThroughServer;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			byte[] cookie = new byte[8];
			Random r = new Random(); r.NextBytes(cookie);

			uint buffersize = 0;
			ushort tlv5size = 0;

      Encoding enc = Marshal.ASCII;
      byte destlength = (byte)enc.GetByteCount(destination);
      ushort messagelength = (ushort)enc.GetByteCount(message);
      byte roomnamelength = (byte)enc.GetByteCount(roominfo.FullName);

			// ICBM header size
			buffersize += (uint)(8 + 2 + 1 + destlength);
			// TLV 0x0005 header
			buffersize += (uint)4;
			// Channel 2 header
			tlv5size += 2 + 8 + 16;
			// The rest of the packet
			tlv5size += (ushort)(6 + 4 + 4 + messagelength + 4 + 2 + 1 + roomnamelength + 2);
			buffersize += tlv5size;

			int index = 0;
			byte[] buffer = new byte[buffersize];
			// ICBM header
			Marshal.CopyArray(cookie, buffer, 0, ref index);
			Marshal.InsertUshort(buffer, 0x0002, ref index);
      buffer[index++] = destlength;
      Marshal.InsertString(buffer, destination, enc, ref index);
			// TLV type 0x0005
			Marshal.InsertUshort(buffer, 0x0005, ref index);
			Marshal.InsertUshort(buffer, (ushort)tlv5size, ref index);
			// TLV type 0x0005 header
			Marshal.InsertUshort(buffer, 0x0000, ref index);
			Marshal.CopyArray(cookie, buffer, 0, ref index);
			Marshal.InsertUint(buffer, 0x748F2420, ref index);
			Marshal.InsertUint(buffer, 0x628711D1, ref index);
			Marshal.InsertUint(buffer, 0x82224445, ref index);
			Marshal.InsertUint(buffer, 0x53540000, ref index);
			// TLV type 0x000A
			Marshal.InsertUshort(buffer, 0x000A, ref index);
			Marshal.InsertUshort(buffer, 0x0002, ref index);
			Marshal.InsertUshort(buffer, 0x0001, ref index);
			// TLV type 0x000F
			Marshal.InsertUshort(buffer, 0x000F, ref index);
			Marshal.InsertUshort(buffer, 0x0000, ref index);
			// TLV type 0x000C - Message to send along
			Marshal.InsertUshort(buffer, 0x000C, ref index);
			Marshal.InsertUshort(buffer, messagelength, ref index);
      Marshal.InsertString(buffer, message, enc, ref index);
			// TLV type 0x2711 - Chat room information
			Marshal.InsertUshort(buffer, 0x2711, ref index);
			Marshal.InsertUshort(buffer, (ushort)(2 + 1 + roomnamelength + 2), ref index);
			Marshal.InsertUshort(buffer, roominfo.Exchange, ref index);
			buffer[index++] = roomnamelength;
      Marshal.InsertString(buffer, roominfo.FullName, enc, ref index);
			Marshal.InsertUshort(buffer, roominfo.Instance, ref index);

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
			SNACFunctions.BuildFLAP(dp);
    }

    #region Direct Connection accept/cancel/invite
    /// <summary>
    /// Accept a direct connection attempt
    /// </summary>
    public static void SendDirectConnectionAccept(Session sess, DirectConnection conn)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
      sh.FamilySubtypeID = (ushort)ICBMService.SendMessageThroughServer;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      Encoding enc = Marshal.ASCII;
      uint buffersize = 0;
      ushort tlv5size = 0;
      int index = 0;

      buffersize += (uint)(8 + 2 + 1 + enc.GetByteCount(conn.Other.ScreenName));
      tlv5size += (ushort)(2 + 8 + 16);
      buffersize += (uint)(4 + tlv5size + 4);

      byte[] buffer = new byte[buffersize];
      Marshal.CopyArray(conn.Cookie, buffer, 0, ref index);
      Marshal.InsertUshort(buffer, 0x0002, ref index);
      buffer[index++] = (byte)enc.GetByteCount(conn.Other.ScreenName);
      Marshal.InsertString(buffer, conn.Other.ScreenName, enc, ref index);
      Marshal.InsertUshort(buffer, 0x0005, ref index);
      Marshal.InsertUshort(buffer, tlv5size, ref index);
      Marshal.InsertUshort(buffer, RendezvousData.UshortFromType(RendezvousType.Accept), ref index);
      Marshal.CopyArray(conn.Cookie, buffer, 0, ref index);
      Marshal.CopyArray(CapabilityProcessor.GetCapabilityArray(conn.Capability), buffer, 0, ref index);
      Marshal.InsertUint(buffer, 0x00030000, ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Cancel a direct connection attempt
    /// </summary>
    public static void SendDirectConnectionCancellation(Session sess, DirectConnection conn, string reason)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
      sh.FamilySubtypeID = (ushort)ICBMService.SendMessageThroughServer;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      Encoding enc = Marshal.ASCII;
      uint buffersize = 0;
      ushort tlv5size = 0;
      int index = 0;

      buffersize += (uint)(8 + 2 + 1 + enc.GetByteCount(conn.Other.ScreenName));
      tlv5size += (ushort)(2 + 8 + 16 + 6 + 4 + enc.GetByteCount(reason));
      buffersize += (uint)(4 + tlv5size + 4);

      byte[] buffer = new byte[buffersize];
      Marshal.CopyArray(conn.Cookie, buffer, 0, ref index);
      Marshal.InsertUshort(buffer, 0x0002, ref index);
      buffer[index++] = (byte)enc.GetByteCount(conn.Other.ScreenName);
      Marshal.InsertString(buffer, conn.Other.ScreenName, enc, ref index);
      Marshal.InsertUshort(buffer, 0x0005, ref index);
      Marshal.InsertUshort(buffer, tlv5size, ref index);
      Marshal.InsertUshort(buffer, RendezvousData.UshortFromType(RendezvousType.Cancel), ref index);
      Marshal.CopyArray(conn.Cookie, buffer, 0, ref index);
      Marshal.CopyArray(CapabilityProcessor.GetCapabilityArray(conn.Capability), buffer, 0, ref index);
      Marshal.InsertUint(buffer, 0x000B0002, ref index);
      Marshal.InsertUshort(buffer, 0x0001, ref index);
      Marshal.InsertUshort(buffer, 0x000C, ref index);
      Marshal.InsertUshort(buffer, (ushort)enc.GetByteCount(reason), ref index);
      Marshal.InsertString(buffer, reason, enc, ref index);
      Marshal.InsertUint(buffer, 0x00030000, ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Sends a direct connection request on channel 2 -- SNAC(04,06)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="rd">A <see cref="RendezvousData"/> object representing
    /// the current DC request</param>
    public static void SendDirectConnectionRequest(Session sess, DirectConnection conn)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
      sh.FamilySubtypeID = (ushort)ICBMService.SendMessageThroughServer;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      uint buffersize = 0;
      ushort tlv5size = 0;
      ushort extdatalen = 0;
      ushort sequence = RendezvousData.UshortFromSequence(conn.Sequence);
      uint complement = 0;
      string[] IPsplit = null;
      string fnencoding = "";
      
      Encoding enc = Marshal.ASCII;
      byte namelength = (byte)enc.GetByteCount(conn.Other.ScreenName);
      ushort encodinglength = 0;
      ushort messagelength = (ushort)(String.IsNullOrEmpty(conn.Message) ? 0 : enc.GetByteCount(conn.Message));
      
      // ICBM header size -- cookie, channel, snl, sn, TLV5 header, TLV3
      buffersize += (uint)(8 + 2 + 1 + namelength + 4 + 4);

      // Only file transfer initiation contains TLV 2722 and 2721 data
      if (conn is FileTransferConnection && conn.Sequence == RendezvousSequence.DirectOrStage1)
      {
        FileTransferConnection ftc = conn as FileTransferConnection;
        fnencoding = ftc.LocalFileNameEncoding.WebName;
        // TLV 0x2722 size
        encodinglength = (ushort)(4 + enc.GetByteCount(fnencoding));

        // TLV 0x2711 size
        extdatalen += (ushort)(4 + 2 + 2 + 4 + ftc.FileHeader.Name.Length + 1);
      }

      // TLV 0x0005 size
      tlv5size += (ushort)(2 + 8 + 16);
      tlv5size += (ushort)(6 + 4 + 8 + 8 + 8 + 6 + 6 + extdatalen + encodinglength);

      // RendezvousData redirects don't include TLV 0x000F or 0x0003
      if (conn.Sequence != RendezvousSequence.DirectOrStage1)
      {
        tlv5size -= 4;
        tlv5size -= 8;
        if (conn.Sequence == RendezvousSequence.Stage3)
        {
          // No port numbers either, TLV 0x0005 and 0x0007
          tlv5size -= 6;
          tlv5size -= 6;
          sequence = 0x0002;
        }
      }

      // We're using a proxy
      if (conn.Method == DirectConnectionMethod.Proxied)
      {
        tlv5size += 4;
      }

      // There's a message to be sent along
      if (messagelength > 0)
      {
        tlv5size += (ushort)(4 + messagelength);
      }

      buffersize += tlv5size;      

      int index = 0;
      
      byte[] buffer = new byte[buffersize];
      // ICBM header
      Marshal.CopyArray(conn.Cookie, buffer, 0, ref index);
      Marshal.InsertUshort(buffer, 0x0002, ref index);
      buffer[index++] = namelength;
      Marshal.InsertString(buffer, conn.Other.ScreenName, enc, ref index);
      // TLV type 0x0005
      Marshal.InsertUshort(buffer, 0x0005, ref index);
      Marshal.InsertUshort(buffer, tlv5size, ref index);
      // TLV 0x0005 body's header -- request type, cookie, CLSID
      Marshal.InsertUshort(buffer, RendezvousData.UshortFromType(RendezvousType.Invite), ref index);
      Marshal.CopyArray(conn.Cookie, buffer, 0, ref index);
      Marshal.CopyArray(
        CapabilityProcessor.GetCapabilityArray(conn.Capability),
        buffer, 0, ref index);
      // TLV type 0x000A
      Marshal.InsertUint(buffer, 0x000A0002, ref index);
      Marshal.InsertUshort(buffer, sequence, ref index);
      // TLV type 0x000F
      if (conn.Sequence == RendezvousSequence.DirectOrStage1)
      {
        Marshal.InsertUint(buffer, 0x000F0000, ref index);
      }

      if (messagelength > 0)
      {
        // TLV type 0x000C
        Marshal.InsertUshort(buffer, 0x000C, ref index);
        Marshal.InsertUshort(buffer, messagelength, ref index);
        Marshal.InsertString(buffer, conn.Message, enc, ref index);
      }

      if (conn.Method == DirectConnectionMethod.Proxied)
      {
        IPsplit = conn.ProxyIP.Split('.');
      }
      else
      {
        IPsplit = conn.ClientIP.Split('.');
      }
      byte shift = 24;
      complement = 0x00000000;
      // Convert the IP into an NBO uint
      for (int i = 0; i < 4; i++)
      {
        byte b = Byte.Parse(IPsplit[i]);
        complement |= (uint)(b << shift);
        shift -= 8;
      }

      // Insert the proxy IP
      Marshal.InsertUint(buffer, 0x00020004, ref index);
      Marshal.InsertUint(buffer, complement, ref index);

      // Insert the bitwise complement of the proxy IP
      complement = ~complement;
      Marshal.InsertUint(buffer, 0x00160004, ref index);
      Marshal.InsertUint(buffer, complement, ref index);

      // TLV type 0x0003 -- IP address
      if (conn.Sequence == RendezvousSequence.DirectOrStage1)
      {
        Marshal.InsertUint(buffer, 0x00030004, ref index);
        IPsplit = conn.ClientIP.Split('.');
        for (int i = 0; i < 4; i++)
          buffer[index++] = Byte.Parse(IPsplit[i]);
      }

      if (conn.Sequence != RendezvousSequence.Stage3)
      {
        // TLV subtype 0x0005 - port
        Marshal.InsertUint(buffer, 0x00050002, ref index);
        Marshal.InsertUshort(buffer, (ushort)conn.Port, ref index);

        // TLV subtype 0x0017 - bitwise complement of the port
        complement = (uint)conn.Port;
        complement = ~complement;
        ushort portcomp = (ushort)(complement & 0x0000FFFF);
        Marshal.InsertUint(buffer, 0x00170002, ref index);
        Marshal.InsertUshort(buffer, portcomp, ref index);
      }

      if (conn.Method == DirectConnectionMethod.Proxied)
      {
        // Insert the flag that says to use the proxy method
        Marshal.InsertUint(buffer, 0x00100000, ref index);
      }

      if (conn is FileTransferConnection && conn.Sequence == RendezvousSequence.DirectOrStage1)
      {
        FileTransferConnection ftc = conn as FileTransferConnection;

        // Extended data
        Marshal.InsertUshort(buffer, 0x2711, ref index);
        Marshal.InsertUshort(buffer, (ushort)(extdatalen - 4), ref index);
        Marshal.InsertUshort(buffer,
          (ushort)((ftc.TotalFiles > 1) ? 0x0002 : 0x0001), ref index);
        Marshal.InsertUshort(buffer, (ushort)ftc.TotalFiles, ref index);
        Marshal.InsertUint(buffer, ftc.TotalFileSize, ref index);
        Marshal.InsertString(buffer, ftc.FileHeader.Name, enc, ref index);
        buffer[index++] = 0x00;

        // Filename encoding
        Marshal.InsertUshort(buffer, 0x2712, ref index);
        Marshal.InsertUshort(buffer, (ushort)(encodinglength - 4), ref index);
        Marshal.InsertString(buffer, fnencoding, enc, ref index);
      }

      // Acknowledgement request
      Marshal.InsertUint(buffer, 0x00030000, ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }
    #endregion

    /// <summary>
    /// Sends a buddy icon on channel 2 -- SNAC(04,06)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="destination">The screenname to receive the icon</param>
    /// <param name="icon">A byte array containing the icon</param>
    /// <param name="timestamp">The icon's timestamp</param>
    /// <param name="checksum">The icon's checksum</param>
    /// <remarks>11-19-05:  Update to use a RendezvousData object</remarks>
    public static void SendIcon(
			Session sess,
			string destination,
			byte[] icon,
			uint timestamp,
			ushort checksum)
    {
      //SNACHeader sh = new SNACHeader();
      //sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
      //sh.FamilySubtypeID = (ushort)ICBMService.SendMessageThroughServer;
      //sh.Flags = 0x0000;
      //sh.RequestID = sess.GetNextRequestID();

      //byte[] cookie = new byte[8];
      //{
      //  (new Random()).NextBytes(cookie);
      //}

      //uint buffersize = 0;
      //ushort tlv5size = 0;

      //// ICBM header size
      //buffersize += (uint)(8 + 2 + 1 + destination.Length);
      //// TLV 0x0005
      //buffersize += (uint)4;
      //// TLV 0x0005 header
      //tlv5size += 2 + 8 + 16;
      //// The rest of TLV 0x0005
      //tlv5size += (ushort)(6 + 4 + 4 + 2 + 2 + 4 + 4 + icon.Length + 14);
      //buffersize += tlv5size;
      //// TLV 0x0003
      //buffersize += (uint)(2 + 2);

      //int index = 0;
      //byte[] buffer = new byte[buffersize];
      //// ICBM header
      //Marshal.CopyArray(cookie, buffer, 0, ref index);
      //Marshal.InsertUshort(buffer, 0x0002, ref index);
      //buffer[index++] = (byte)destination.Length;
      //Marshal.InsertString(buffer, destination, ref index);
      //// TLV type 0x0005
      //Marshal.InsertUshort(buffer, 0x0005, ref index);
      //Marshal.InsertUshort(buffer, (ushort)tlv5size, ref index);
      //// TLV type 0x0005 header
      //Marshal.InsertUshort(buffer, 0x0000, ref index);
      //Marshal.CopyArray(cookie, buffer, 0, ref index);
      //Marshal.InsertUint(buffer, 0x09461346, ref index);
      //Marshal.InsertUint(buffer, 0x4C7F11D1, ref index);
      //Marshal.InsertUint(buffer, 0x82224445, ref index);
      //Marshal.InsertUint(buffer, 0x53540000, ref index);
      //// TLV type 0x000A
      //Marshal.InsertUshort(buffer, 0x000A, ref index);
      //Marshal.InsertUshort(buffer, 0x0002, ref index);
      //Marshal.InsertUshort(buffer, 0x0001, ref index);
      //// TLV type 0x000F
      //Marshal.InsertUshort(buffer, 0x000F, ref index);
      //Marshal.InsertUshort(buffer, 0x0000, ref index);
      //// TLV type 0x2711
      //Marshal.InsertUshort(buffer, 0x2711, ref index);
      //Marshal.InsertUshort(buffer, (ushort)(2 + 2 + 4 + 4 + icon.Length + 14), ref index);
      //Marshal.InsertUshort(buffer, 0x0000, ref index);
      //Marshal.InsertUshort(buffer, checksum, ref index);
      //Marshal.InsertUint(buffer, (uint)icon.Length, ref index);
      //Marshal.InsertUint(buffer, timestamp, ref index);
      //Marshal.CopyArray(icon, buffer, 0, ref index);
      //Marshal.InsertString(buffer, "AVT1picture.id", ref index);
      //// TLV type 0x0003
      //Marshal.InsertUshort(buffer, 0x0003, ref index);
      //Marshal.InsertUshort(buffer, 0x0000, ref index);

      //DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      //SNACFunctions.BuildFLAP(dp);
    }
		#endregion

		#region SNAC(04,07) -- Receive ICBM
		/// <summary>
		/// Processes an ICBM message sent by the server -- SNAC(04,07)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(04,07)</param>
		public static void ProcessMessage(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			// Pull apart the fixed part of the message
			byte[] message_cookie = new byte[8];
			Marshal.CopyArray(SNACData, message_cookie, ref index);
			ushort channel = Marshal.ByteArrayToUshort(SNACData, ref index);

			UserInfo ui = Marshal.GetUserinfoBlock(SNACData, ref index);

			// End of the fixed part. Pull apart the channel-specific data
			switch(channel)
			{
				case 0x0001:
					SNAC04.ProcessChannelOneMessage(dp.ParentSession, SNACData, index, ui);
					break;
        case 0x0002:
          SNAC04.ProcessChannelTwoMessage(dp.ParentSession, SNACData, index, ui);
          break;
				default:
					dp.ParentSession.OnWarning(ServerErrorCode.UnknownMessageChannel);
					break;
			}
    }

    #region SNAC(04,07):01
    /// <summary>
		/// Processes an incoming ICBM message on channel 1 -- SNAC(04,07)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="SNACData">The SNAC data buffer</param>
		/// <param name="index">The index in the buffer at which the channel-specific data begins</param>
		/// <param name="ui">The UserInfo block that came with this message</param>
		internal static void ProcessChannelOneMessage(Session sess, byte[] SNACData, int index, UserInfo ui)
		{
			ushort key, keylength;
      IM message = new IM(ui);

			MessageFlags flags = MessageFlags.None;
      Encoding encoding = Marshal.ASCII;

			uint icon_length = 0, icon_stamp = 0;
			ushort icon_checksum = 0;

			while(index < SNACData.Length)
			{
				key = Marshal.ByteArrayToUshort(SNACData, ref index);
				keylength = Marshal.ByteArrayToUshort(SNACData, ref index);
				switch(key)
				{
					case 0x0002:	// Message data
						GetChannelOneMessage(SNACData, ref index, keylength, ref message);
						break;
					case 0x0003:	// Server Ack requested by sender (why do we care?)
						break;
					case 0x0004:	// Auto response
            message.IsAutoResponse = true;
						break;
					case 0x0006:	// Message was received offline...ICQ only, probably?
            message.WasReceivedOffline = true;
						break;
					case 0x0008:	// Sender has a buddy icon
						icon_length = Marshal.ByteArrayToUint(SNACData, ref index);
						index += 2;	// Skip 0x0001
						icon_checksum = Marshal.ByteArrayToUshort(SNACData, ref index);
						icon_stamp = Marshal.ByteArrayToUint(SNACData, ref index);
            //if(icon_length > 0)
            //  flags |= MessageFlags.HasIcon;
						break;
					default:		// Unhandled
						index += keylength;
						break;
				}
			}			

			// That's it, send the message to the client
			sess.OnMessageReceived(message);
		}

		/// <summary>
		/// Retrieves the message text from SNAC(04,07) TLV 02
		/// </summary>
		/// <param name="buffer">A byte buffer</param>
		/// <param name="index">The index in the buffer at which SNAC(04,07) TLV 02 begins</param>
		/// <param name="keylength">The length of the TLV</param>
		internal static void GetChannelOneMessage(byte[] buffer, ref int index, int keylength, ref IM message)
		{
			byte[] required_capabilities = null;	// libfaim doesn't interpret these, I won't either

			uint endindex = (uint)(index + keylength);
			while(index < endindex)
			{
				byte identifier = buffer[index++];
				byte version = buffer[index++];
				ushort length = Marshal.ByteArrayToUshort(buffer, ref index);
				switch(identifier)
				{
					case 0x05:	// Required capability array
						required_capabilities = new byte[length];
						Marshal.CopyArray(buffer, required_capabilities, ref index);
						break;
					case 0x01:	// Message text
            ushort charset = Marshal.ByteArrayToUshort(buffer, ref index);
            ushort charsubset = Marshal.ByteArrayToUshort(buffer, ref index);
            message.Encoding = IM.GetEncodingFromCharset(charset, charsubset);
            message.Message = Marshal.ByteArrayToString(buffer, ref index, length - 4, message.Encoding);
						break;
					default:	// Unhandled case
						index += length;
						break;
				}
			}
    }
    #endregion

    #region SNAC(04,07):02
    /// <summary>
    /// Process a ICBM that was sent over channel two - rich text messages, chat/filetransfer invites, buddy icons
    /// </summary>
    internal static void ProcessChannelTwoMessage(Session sess, byte[] SNACData, int index, UserInfo ui)
    {
      ushort key, keylength;
      ushort errorcode = 0xFFFF;
      DirectConnection conn = null;

      while (index < SNACData.Length)
      {
        key = Marshal.ByteArrayToUshort(SNACData, ref index);
        keylength = Marshal.ByteArrayToUshort(SNACData, ref index);

        switch (key)
        {
          case 0x0005:
            conn = ProcessChannel2Tlv05(sess, SNACData, ref index, keylength, ui);
            break;

          case 0x000B:
            errorcode = Marshal.ByteArrayToUshort(SNACData, ref index);
            break;

          default:
            index += keylength;
            break;
        }
      }
    }

    /// <summary>
    /// Processes the inner TLV list in TLV 0x0005 and returns a populated RendezvousData structure
    /// </summary>
    internal static DirectConnection ProcessChannel2Tlv05(Session sess, byte[] SNACData, ref int index, int datalength, UserInfo ui)
    {
      byte[] tlv2711 = null;
      Encoding tlv2712 = Marshal.ASCII;
      byte[] invitemessage = null;
      byte[] caparray = new byte[16];
      Encoding encoding = Marshal.ASCII;
      string language = "en";
      uint bitflippedip = 0;
      ushort bitflippedport = 0;
      ushort key, keylength;

      byte[] cookie = new byte[8];
      DirectConnection directconn = null;
      Capabilities capa = Capabilities.None;
      RendezvousType rtype = RendezvousType.Unknown;

      int stopindex = index + datalength;
      
      // Pull the type, cookie, and capability array
      rtype = RendezvousData.TypeFromUshort(Marshal.ByteArrayToUshort(SNACData, ref index));
      Marshal.CopyArray(SNACData, cookie, ref index);
      Marshal.CopyArray(SNACData, caparray, ref index);
      capa = CapabilityProcessor.ProcessCLSIDList(caparray);

      // Create the correct type of connection based on the capability
      if (capa == Capabilities.SendFiles)
      {
        if((directconn = sess.Connections.GetDirectConnectionByCookie(cookie)) == null)
        {
          directconn = sess.Connections.CreateNewFileTransferConnection(
            DirectConnectionMethod.Direct, DirectConnectRole.Receiver);
        }
      }
      else if (capa == Capabilities.DirectIM)
      {
        //rd.DirectConnection = new DirectConnectInfo(rd, DirectConnectType.DirectIM);
        sess.OnWarning(ServerErrorCode.UnknownRendezvousChannel);
        index += datalength;
        return null;
      }
      else if (capa == Capabilities.Chat)
      {
        directconn = sess.Connections.CreateNewChatInvitationConnection(DirectConnectRole.Receiver);
      }
      else
      {
        // Currently unsupported
        sess.OnWarning(ServerErrorCode.UnknownRendezvousChannel);
        index += datalength;
        return null;
      }

      directconn.Other = ui;
      directconn.Cookie = cookie;
      directconn.Type = rtype;

      // Process the inner TLV list
      while (index < stopindex)
      {
        key = Marshal.ByteArrayToUshort(SNACData, ref index);
        keylength = Marshal.ByteArrayToUshort(SNACData, ref index);

        switch (key)
        {
          case 0x0002:
            directconn.ProxyIP = Marshal.UIntToIPAddress(Marshal.ByteArrayToUint(SNACData, ref index));
            break;

          case 0x0003:
            directconn.ClientIP = Marshal.UIntToIPAddress(Marshal.ByteArrayToUint(SNACData, ref index));
            break;

          case 0x0004:
            directconn.VerifiedIP = Marshal.UIntToIPAddress(Marshal.ByteArrayToUint(SNACData, ref index));
            break;

          case 0x0005:
            directconn.Port = Marshal.ByteArrayToUshort(SNACData, ref index);
            break;

          case 0x000A:
            directconn.Sequence = RendezvousData.SequenceFromUshort(Marshal.ByteArrayToUshort(SNACData, ref index));
            break;

          case 0x000C:
            invitemessage = new byte[keylength];
            Marshal.CopyArray(SNACData, invitemessage, ref index);
            break;

          case 0x000D:
            encoding = Encoding.GetEncoding(Marshal.ByteArrayToString(SNACData, ref index, keylength, Marshal.ASCII));
            break;

          case 0x000E:
            language = Marshal.ByteArrayToString(SNACData, ref index, keylength, Marshal.ASCII);
            break;

          case 0x0010:
            directconn.Method = DirectConnectionMethod.Proxied;
            break;

          case 0x0016:
            // OscarLib reads this but basically ignores it why not, it's used to verify the IP
            bitflippedip = Marshal.ByteArrayToUint(SNACData, ref index);
            break;

          case 0x0017:
            // Same deal as above but for the port
            bitflippedport = Marshal.ByteArrayToUshort(SNACData, ref index);
            break;

          case 0x2711:  // Service data
            tlv2711 = new byte[keylength];
            Marshal.CopyArray(SNACData, tlv2711, ref index);
            break;

          case 0x2712:  // Service data encoding
            tlv2712 = Encoding.GetEncoding(Marshal.ByteArrayToString(SNACData, ref index, keylength, Marshal.ASCII));
            break;

          default:
            index += keylength;
            break;
        }
      }

      if (invitemessage != null)
      {
        directconn.Message = encoding.GetString(invitemessage, 0, invitemessage.Length);
      }

      // Process the extra data, if necessary
      if (directconn is FileTransferConnection)
      {
        ProcessDirectConnectionRequest(sess, directconn as FileTransferConnection, tlv2711);
      }
      else if (directconn is ChatInvitationConnection)
      {
        ProcessChatInvitationRequest(sess, directconn as ChatInvitationConnection, tlv2711);
      }

      return directconn;
    }

    /// <summary>
    /// Performs processing on the 0x2711 TLV of a chat invitation request
    /// </summary>
    internal static void ProcessChatInvitationRequest(Session sess, ChatInvitationConnection conn, byte[] servicedata)
    {
      if (conn.Type == RendezvousType.Accept)
      {
        // Accepting chat invitation. Does this ever get received?
      }
      else if (conn.Type == RendezvousType.Cancel)
      {
        // Cancelling chat invitation. Jerks.
      }
      else if (conn.Type == RendezvousType.Accept && servicedata != null)
      {
        conn.ChatRoom = new ChatRoomInfo();
        int index = 0;
        conn.ChatRoom.Exchange = Marshal.ByteArrayToUshort(servicedata, ref index);
        byte strlen = servicedata[index++];
        conn.ChatRoom.FullName = Marshal.ByteArrayToString(servicedata, ref index, strlen, Marshal.ASCII);
        conn.ChatRoom.DisplayName = ChatRoomInfo.GetDisplayName(conn.ChatRoom.FullName);
        conn.ChatRoom.Instance = Marshal.ByteArrayToUshort(servicedata, ref index);

        sess.OnChatInvitationReceived(conn.Other, conn.ChatRoom.DisplayName,
          conn.ChatInvite.Message, conn.ChatInvite.Encoding, conn.ChatInvite.Language,
          conn.Cookie);
      }
    }

    /// <summary>
    /// Performs TLV 0x2711 processing for direct connect (sendfiles, DirectIM) negotiation
    /// </summary>
    internal static void ProcessDirectConnectionRequest(Session sess, FileTransferConnection conn, byte[] servicedata)
    {
      if (conn.Type == RendezvousType.Accept)
      {
        // They're accepting, which means we're going to get a connection on the
        // listener socket set up in FileTransferManager. Do nothing here
      }
      else if (conn.Type == RendezvousType.Cancel)
      {
        FileTransferConnection cancelled = sess.Connections.GetDirectConnectionByCookie(conn.Cookie) as FileTransferConnection;

        if(cancelled != null)
        {
          cancelled.CancelFileTransfer("Remote user cancelled direct connection");
        }

        return;
      }
      else if(conn.Type == RendezvousType.Invite)
      {
        // AIM sends a type 0x0000 when accepting...huh.

        if (conn.Sequence == RendezvousSequence.DirectOrStage1)
        {
          if (servicedata == null)
          {
            return;
          }

          int index = 0;
          conn.SubType = Marshal.ByteArrayToUshort(servicedata, ref index);
          conn.TotalFiles = Marshal.ByteArrayToUshort(servicedata, ref index);
          conn.TotalFileSize = Marshal.ByteArrayToUint(servicedata, ref index);

          int strlen = 0, startindex = index;
          // The filename in an RD invite is null-terminated ASCII
          while (servicedata[startindex++] != 0x00)
            strlen++;
          conn.FileHeader.Name = Marshal.ByteArrayToString(servicedata, ref index, strlen, Marshal.ASCII);

          sess.OnDirectConnectionRequestReceived(conn.Cookie);
        }
        else if (conn.Sequence == RendezvousSequence.Stage2)
        {
          // The receipient of a previous invite wants a stage 2 proxy redirection
          // Get the old one and copy over the new info, then replace the new with the old
          //FileTransferConnection old = sess.Connections.GetDirectConnectionByCookie(
          //  RendezvousManager.GetKeyFromCookie(conn.Cookie), true) as FileTransferConnection;

          //old.ProxyIP = conn.ProxyIP;
          //old.Port = conn.Port;
          //old.Method = DirectConnectionMethod.Proxied;
          //conn = old;

          // Shut down the server socket, we won't be getting a connection on it
          conn.StopListeningSocket();
          SNAC04.SendDirectConnectionAccept(sess, conn);
          conn.StartSendThroughStage2Proxy();
        }
        else if (conn.Sequence == RendezvousSequence.Stage3)
        {
          // Direct connection and proxy 2 failed, the sender's trying to proxy it now
          conn.Method = DirectConnectionMethod.Proxied;
          conn.ConnectToServer();
        }
      }
    }

    // End region SNAC(04,07):02
    #endregion

    // End region SNAC(04,07)
    #endregion

    /// <summary>
		/// Sends a warning to a remote client -- SNAC(04,08)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="screenname">The screenname of the client to warn</param>
		/// <param name="anonymous">Send the warning as anonymous or as yourself</param>
		public static void SendWarning(Session sess, string screenname, bool anonymous)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
			sh.FamilySubtypeID = (ushort)ICBMService.EvilRequest;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

      Encoding enc = Marshal.ASCII;
      byte screennamelength = (byte)enc.GetByteCount(screenname);

			int index = 0;
			byte[] buffer = new byte[2 + 1 + screennamelength];
			Marshal.InsertUshort(buffer, (ushort)((anonymous) ? 1 : 0), ref index);
			buffer[index++] = screennamelength;
			Marshal.InsertString(buffer, screenname, enc, ref index);

			// Save the screenname so the handler for the reply SNAC can retrieve it
			sess.StoreRequestID(sh.RequestID, screenname);

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Processes the acknowledgement of a warning sent by the server -- SNAC(04,09)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(04,09)</param>
		public static void ProcessWarningAcknowledgement(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			uint requestid = Marshal.ByteArrayToUint(SNACData, 6);
			ushort warning_level_gain = Marshal.ByteArrayToUshort(SNACData, 0);
			ushort new_warning_level = Marshal.ByteArrayToUshort(SNACData, 2);
			dp.ParentSession.OnWarningAcknowledgement(
				requestid,
				warning_level_gain,
				new_warning_level);
		}

		/// <summary>
		/// Processes an undeliverable message notification sent by the server -- SNAC(04,0A)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(04,0A)</param>
		public static void ProcessUndeliverableMessage(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			while(index < SNACData.Length)
			{
				ushort messagechannel = Marshal.ByteArrayToUshort(SNACData, ref index);
				UserInfo ui = Marshal.GetUserinfoBlock(SNACData, ref index);
				ushort numbermissed = Marshal.ByteArrayToUshort(SNACData, ref index);
				UndeliverableMessageReason reason = 
					(UndeliverableMessageReason)Marshal.ByteArrayToUshort(SNACData, ref index);

				dp.ParentSession.OnUndeliverableMessage(messagechannel, ui, numbermissed, reason);
			}
		}

		/// <summary>
		/// Processes an undeliverable message notification sent by the server -- SNAC(04,0C)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(04,0C)</param>
		public static void ProcessServerAcknowledgement(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			byte[] cookie = new byte[8];
			Marshal.CopyArray(SNACData, cookie, ref index);
			ushort messagechannel = Marshal.ByteArrayToUshort(SNACData, ref index);
			byte snlength = SNACData[index++];
      string screenname = Marshal.ByteArrayToString(SNACData, ref index, (int)snlength, Marshal.ASCII);

			dp.ParentSession.OnMessageAccepted(messagechannel, screenname);
		}

		/// <summary>
		/// Processes a typing notification sent by the server -- SNAC(04,14)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(04,14)</param>
		public static void ProcessTypingNotification(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			// Pull apart SNAC(04,14)
			int index = 0;
			byte[] cookie = new byte[8];
			Marshal.CopyArray(SNACData, cookie, ref index);
			ushort notification_channel = Marshal.ByteArrayToUshort(SNACData, ref index);
			byte sn_length = SNACData[index++];
      string screenname = Marshal.ByteArrayToString(SNACData, ref index, sn_length, Marshal.ASCII);
			TypingNotification tn = (TypingNotification)Marshal.ByteArrayToUshort(SNACData, ref index);

			// Raise an event
			dp.ParentSession.OnTypingNotification(screenname, tn);
		}

		/// <summary>
		/// Sends a typing notification to a remote client -- SNAC(04,14)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="screenname">The screenname to receive the typing notification</param>
		/// <param name="tn">The <see cref="TypingNotification"/> to be sent</param>
		public static void SendTypingNotification(Session sess, string screenname, TypingNotification tn)
		{
			// Construct SNAC(04,14)
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.ICBMService;
			sh.FamilySubtypeID = (ushort)ICBMService.MiniTypingNotifications;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

      byte screennamelength = (byte)Marshal.ASCII.GetByteCount(screenname);

			int index;
			byte[] buffer = new byte[8 + 2 + 1 + screennamelength + 2];
			for(index = 0; index < 8; index++)
			{
				buffer[index] = 0x00;
			}
			Marshal.InsertUshort(buffer, 0x0001, ref index);
			buffer[index++] = screennamelength;
      Marshal.InsertString(buffer, screenname, Marshal.ASCII, ref index);
      Marshal.InsertUshort(buffer, (ushort)tn, ref index);

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
			SNACFunctions.BuildFLAP(dp);
		}
	}
}
